Skip to main content

Anti Pattern

An anti-pattern is a common solution to a recurring problem that initially seems appropriate but ultimately results in poor outcomes. These patterns often lead to inefficiencies, difficult-to-maintain code, or system instability. While they might appear to solve the issue at first, they usually introduce long-term technical debt, making the system more complex, error-prone, or harder to maintain.

Why Anti-Patterns Occur​

  1. Inexperience: Developers might use poor solutions due to a lack of experience or understanding of best practices.
  2. Time Pressure: When under tight deadlines, teams might choose shortcuts that lead to anti-patterns.
  3. Lack of Refactoring: Code can degrade into anti-patterns if not regularly refactored or cleaned up.
  4. Complex Requirements: Sometimes complex or unclear requirements push developers to adopt convoluted or inefficient solutions.

Common Anti-Patterns​

  1. Spaghetti Code:

    • Definition: Code with a disorganized, tangled structure, making it hard to read, understand, and maintain. It often occurs when developers don't follow a clear architectural design.
    • Effect: Hard to debug and modify, increasing the likelihood of bugs when making changes.
  2. God Object (or God Class):

    • Definition: A single class or object that handles too many responsibilities and knows too much about other parts of the system, violating the Single Responsibility Principle.
    • Effect: Makes code difficult to extend and maintain because any change to one feature in the God Object risks breaking unrelated parts of the system.
  3. Golden Hammer:

    • Definition: Over-relying on a single technology, framework, or solution for all problems, even when it's not the best fit.
    • Effect: Leads to inefficiencies, as some problems require specialized tools or approaches.
  4. Lava Flow:

    • Definition: Dead or outdated code that is left in the system because developers are afraid to remove it, fearing it might break the application.
    • Effect: Increases code complexity and introduces potential bugs when the outdated code interferes with newer code.
  5. Copy-Paste Programming:

    • Definition: Copying code from one section of the program to another without abstracting the logic into reusable methods or components.
    • Effect: Leads to code duplication, making the codebase difficult to maintain, as any changes need to be made in multiple places.
  6. Reinventing the Wheel:

    • Definition: Developers create custom solutions for problems that are already solved by existing libraries, frameworks, or tools.
    • Effect: Wastes time and effort, and custom solutions are often less optimized and tested than existing, mature solutions.

Example: God Object Anti-Pattern​

Let's look at an example in Java, where a God Object has taken on too many responsibilities. In this case, the OrderProcessor class handles not only order processing but also payment, inventory, and customer notifications.

Original Code (God Object)​

public class OrderProcessor {

public void processOrder(String productId, int quantity, String customerEmail) {
// Process payment
System.out.println("Processing payment...");

// Update inventory
System.out.println("Updating inventory...");

// Send notification to customer
System.out.println("Sending notification to customer: " + customerEmail);
}
}

Problems:

  1. Single Responsibility Violation: The OrderProcessor class is doing too much. It's handling payment processing, inventory management, and sending customer notifications—all of which should be separate concerns.
  2. Difficult to Extend: Any change to one feature, such as updating inventory, might break payment processing or notifications.
  3. Hard to Test: It's difficult to test the individual parts in isolation because everything is bundled together

Refactored Code (Following SOLID Principles)​

Let’s refactor the code to address the God Object anti-pattern. We’ll extract different responsibilities into separate classes.

// Separate class for payment processing
public class PaymentProcessor {
public void processPayment(String productId, int quantity) {
System.out.println("Processing payment for product: " + productId);
}
}

// Separate class for inventory management
public class InventoryManager {
public void updateInventory(String productId, int quantity) {
System.out.println("Updating inventory for product: " + productId);
}
}

// Separate class for customer notifications
public class CustomerNotifier {
public void sendNotification(String customerEmail) {
System.out.println("Sending notification to customer: " + customerEmail);
}
}

// Refactored OrderProcessor class delegating responsibilities
public class OrderProcessor {

private PaymentProcessor paymentProcessor;
private InventoryManager inventoryManager;
private CustomerNotifier customerNotifier;

public OrderProcessor(PaymentProcessor paymentProcessor, InventoryManager inventoryManager, CustomerNotifier customerNotifier) {
this.paymentProcessor = paymentProcessor;
this.inventoryManager = inventoryManager;
this.customerNotifier = customerNotifier;
}

public void processOrder(String productId, int quantity, String customerEmail) {
paymentProcessor.processPayment(productId, quantity);
inventoryManager.updateInventory(productId, quantity);
customerNotifier.sendNotification(customerEmail);
}
}

Avoiding Anti-Patterns​

  1. Follow SOLID Principles: These design principles help you structure your code to avoid common anti-patterns like the God Object or Spaghetti Code.
  2. Frequent Refactoring: Regularly refactor your code to ensure it stays clean and maintainable.
  3. Peer Reviews: Code reviews help catch anti-patterns early before they spread across the codebase.
  4. Write Unit Tests: Good tests help ensure that your refactoring doesn’t introduce new bugs and that the system's external behavior remains consistent.
  5. Learn from Design Patterns: Use design patterns (e.g., Factory, Observer, Strategy) to solve common problems in a structured and maintainable way.